package com.snow.jpa;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONObject;
import com.snow.conditions.ConditionType;
import com.snow.conditions.LinkType;
import com.snow.conditions.QueryConditionField;
import com.snow.conditions.QueryOrder;
import com.snow.exception.CommonRuntimeException;
import com.snow.jpa.model.IBaseSearchParams;
import com.snow.model.ILoginUser;
import com.snow.view.PageData;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
public class HqlQueryBuilder {
    private HqlQueryBuilder(Table mainTable) {
        this.mainTable = mainTable;
    }

    @Getter
    @Setter
    static class Table {
        private String entityName;

        private String alias;

        public Table(String entityName, String alias) {
            this.entityName = entityName;
            this.alias = alias;
        }
    }

    @Getter
    @Setter
    @AllArgsConstructor
    public static class Where {
        private LinkType linkType = LinkType.AND;
        private List<Where> whereList = new ArrayList<>();
        private HqlQueryBuilder builder;
        private String column = "";
        private ConditionType type = ConditionType.subQuery;
        private Object value;
        private String dateFormat = "yyyy-MM-dd";

        public Where(HqlQueryBuilder builder) {
            this.builder = builder;
            this.whereList = new ArrayList<>();
        }

        public Where(HqlQueryBuilder builder, String column, ConditionType type, Object value) {
            this.builder = builder;
            this.column = column;
            this.type = type;
            this.value = value;
        }

        public HqlQueryBuilder end() {
            return this.getBuilder();
        }

        public Where or() {
            this.linkType = LinkType.OR;
            return this;
        }

        public Where and() {
            this.linkType = LinkType.AND;
            return this;
        }

        public Where subQuery(Consumer<Where> subQueryBuilder) {
            Where subQuery = new Where(this.getBuilder());
            subQueryBuilder.accept(subQuery);
            this.whereList.add(subQuery);
            return this;
        }

        public Where equal(String columnName, Object value) {
            return condition(ConditionType.equal, columnName, value);
        }

        public Where like(String columnName, String value) {
            return condition(ConditionType.like, columnName, value);
        }

        public Where isNull(String columnName) {
            return condition(ConditionType.isNull, columnName, null);
        }

        public Where isNotNull(String columnName) {
            return condition(ConditionType.isNotNull, columnName, null);
        }

        public Where condition(ConditionType type, String columnName, Object value) {
            this.whereList.add(new Where(this.builder, columnName, type, value));
            return this;
        }

        public String toHql() {
            return this.getBuilder().toHql();
        }

        public List<?> query() {
            return this.getBuilder().query();
        }

        public List<Map<String, Object>> query(String... keys) {
            return this.getBuilder().query(keys);
        }

        public <T> List<T> query(Function<Object, T> function) {
            return this.getBuilder().query(function);
        }

        public Object one() {
            return this.getBuilder().one();
        }

        public JSONObject one(String... keys) {
            return this.getBuilder().one(keys);
        }

        public <E> List<E> query(Class<E> clazz) {
            return this.getBuilder().query(clazz);
        }

        public PageData<?> page(Integer page, Integer size) {
            return this.getBuilder().page(page, size);
        }

        public PageData<JSONObject> page(Integer page, Integer size, String... keys) {
            return this.getBuilder().page(page, size, keys);
        }

        public <T> PageData<T> page(Integer page, Integer size, Function<Object, T> function) {
            return this.getBuilder().page(page, size, function);
        }
    }


    private final Table mainTable;

    private String select;
    private String groupByStr = "";
    private String pageTotalStr = "";
    private boolean enableDataScopeFilter = false;
    private String dataScopeFilterAlias = "";
    private ILoginUser loginUser = null;

    private List<String> join = new ArrayList<>();

    private Where where = null;

    private List<QueryOrder> orders = new ArrayList<>();


    public HqlQueryBuilder select(String select) {
        this.select = select;
        return this;
    }

    public Where where() {
        if (this.where == null) {
            this.where = new Where(this);
        }
        return this.where;
    }

    public HqlQueryBuilder enableDataScope(String alias, ILoginUser loginUser) {
        this.enableDataScopeFilter = true;
        this.dataScopeFilterAlias = alias;
        this.loginUser = loginUser;
        return this;
    }

    public HqlQueryBuilder where(IBaseSearchParams searchParams, String alias) {
        Where where = this.where();
        for (Field declaredField : searchParams.getClass().getDeclaredFields()) {
            Object value = getFieldValue(searchParams, declaredField);
            if (StrUtil.isBlankIfStr(value)) {
                log.debug("值为空跳过");
                continue;
            }
            if (AnnotationUtil.hasAnnotation(declaredField, QueryConditionField.class)) {
                QueryConditionField queryConditionField = AnnotationUtil.getAnnotation(declaredField, QueryConditionField.class);
                where.condition(queryConditionField.type(), alias + "." + StrUtil.blankToDefault(queryConditionField.value(), declaredField.getName()), value);
            } else {
                log.debug("未添加查询注解，跳过");
            }
        }
        return where.end();
    }

    public HqlQueryBuilder disTenantFilter() {
        LambdaQueryBuilder.disableTenantFilter();
        return this;
    }

    public HqlQueryBuilder disDeptDataScopeFilter() {
        LambdaQueryBuilder.disableDeptDatascopeFilter();
        return this;
    }

    public HqlQueryBuilder groupBy(String groupBy) {
        return this.groupBy(groupBy, "");
    }

    public HqlQueryBuilder groupBy(String groupBy, String countStr) {
        this.groupByStr = groupBy;
        this.pageTotalStr = countStr;
        return this;
    }


    Object getFieldValue(Object data, Field field) {
        try {
            return ReflectUtil.getFieldValue(data, field);
        } catch (UtilException e) {
            Method getMethod = ReflectUtil.getMethodIgnoreCase(data.getClass(), "get" + field.getName());
            return ReflectUtil.invoke(data, getMethod);
        }
    }

    public HqlQueryBuilder join(String entityName, String alias, String on) {
        this.join.add("join " + entityName + " " + alias + " on " + on);
        return this;
    }

    public HqlQueryBuilder leftJoin(String entityName, String alias, String on) {
        this.join.add("left join " + entityName + " " + alias + " on " + on);
        return this;
    }

    public HqlQueryBuilder leftJoin(Class<?> entityClass, String alias, String on) {
        this.join.add("left join " + HqlQueryBuilder.getEntityName(entityClass) + " " + alias + " on " + on);
        return this;
    }

    public HqlQueryBuilder rightJoin(String entityName, String alias, String on) {
        this.join.add("right join " + entityName + " " + alias + " on " + on);
        return this;
    }

    public HqlQueryBuilder asc(String column) {
        this.orders.add(QueryOrder.asc(column));
        return this;
    }

    public HqlQueryBuilder desc(String column) {
        this.orders.add(QueryOrder.desc(column));
        return this;
    }

    public static HqlQueryBuilder builder(String mainEntity, String alias) {
        return builder(mainEntity, alias, null);
    }

    public static HqlQueryBuilder builder(String mainEntity, String alias, ILoginUser loginUser) {
        HqlQueryBuilder builder = new HqlQueryBuilder(new Table(mainEntity, alias));
        builder.select(alias);
        if (loginUser != null) {
            builder.enableDataScope(alias, loginUser);
        }
        return builder;
    }

    public String toHql() {
        StringBuilder builder = new StringBuilder();
        builder.append("select ").append(this.select);
        builder.append(" from ").append(this.mainTable.getEntityName()).append(" ").append(this.mainTable.getAlias());
        if (this.join != null && !this.join.isEmpty()) {
            for (String join : this.join) {
                builder.append(" ").append(join);
            }
        }
        if (this.where != null && !this.where.getWhereList().isEmpty()) {
            builder.append(" where ")
                    .append(this.buildHqlWhere(this.where));
        }
        if (this.enableDataScopeFilter) {
            if (this.where != null && !this.where.getWhereList().isEmpty()) {
                builder.append(" and ( ");
            } else {
                builder.append(" where ");
            }
            if (this.loginUser.getDataScope() == null || this.loginUser.getDataScope() == 0) {
                builder.append(" ").append(this.dataScopeFilterAlias).append(".tenantId = ")
                        .append(this.loginUser.getTenantId())
                        .append(" ");
            } else {
                builder.append(" ").append(this.dataScopeFilterAlias).append(".tenantId = ")
                        .append(this.loginUser.getTenantId()).append(" and ").append(this.dataScopeFilterAlias).append(".deptId = ")
                        .append(this.loginUser.getDeptId())
                        .append(" ");

            }
            if (this.where != null && !this.where.getWhereList().isEmpty()) {
                builder.append(" ) ");
            }
        }
        if (StrUtil.isNotBlank(this.groupByStr)) {
            builder.append(" group by ")
                    .append(this.groupByStr);
        }
        if (this.orders != null && !this.orders.isEmpty()) {
            builder.append(" order by ");
            for (int i = 0; i < this.orders.size(); i++) {
                QueryOrder order = this.orders.get(i);
                builder.append(" " + order.getColumn() + " " + order.getType());
                if (i != this.orders.size() - 1) {
                    builder.append(",");
                }
            }
        }
        String hql = builder.toString();
        log.debug("hql:{}", hql);
        return hql;
    }


    public List<?> query() {
        Query query = SpringUtil.getBean(EntityManager.class)
                .createQuery(this.toHql());
        return query.getResultList();
    }

    public List<Map<String, Object>> query(String... keys) {
        List<?> list = query();
        if (list == null) {
            return new ArrayList<>();
        }
        List<Map<String, Object>> results = new ArrayList<>();
        for (Object o : list) {
            Map<String, Object> item = new JSONObject();
            for (int i = 0; i < keys.length; i++) {
                item.put(keys[i], ArrayUtil.get(o, i));
            }
            results.add(item);
        }
        return results;
    }

    public <T> List<T> query(Function<Object, T> function) {
        List<?> list = query();
        if (list == null) {
            return new ArrayList<>();
        }
        return list.stream().map(e -> function.apply(e)).collect(Collectors.toList());
    }

    public Object one() {
        List<?> list = this.query();
        if (list.isEmpty()) {
            return null;
        } else {
            return list.get(0);
        }
    }

    public JSONObject one(String... keys) {
        Object obj = one();
        if (obj == null) {
            return null;
        }
        JSONObject result = new JSONObject();
        for (int i = 0; i < keys.length; i++) {
            String key = keys[i];
            result.set(key, ArrayUtil.get(obj, i));
        }
        return result;
    }

    public <E> List<E> query(Class<E> clazz) {
        Query query = SpringUtil.getBean(EntityManager.class)
                .createQuery(this.toHql());
        return (List<E>) query.getResultList();
    }

    /**
     * @param page 从1 开始
     * @param size 不能小于1
     */
    public PageData<?> page(Integer page, Integer size) {
        return page(page, size, e -> e);
    }

    public PageData<JSONObject> page(Integer page, Integer size, String... keys) {
        return page(page, size, o -> {
            JSONObject row = new JSONObject();
            for (int i = 0; i < keys.length; i++) {
                row.set(keys[i], ArrayUtil.get(o, i));
            }
            return row;
        });
    }

    /**
     * @param page 从1 开始
     * @param size 不能小于1
     */

    public <T> PageData<T> page(Integer page, Integer size, Function<Object, T> function) {
        if (page < 1) {
            throw new CommonRuntimeException("page页码值必须从1开始");
        }
        EntityManager entityManager = SpringUtil.getBean(EntityManager.class);
        String hqlString = this.toHql();
        List list = entityManager
                .createQuery(hqlString)
                .setFirstResult((page - 1) * size)
                .setMaxResults(size)
                .getResultList();
        if (hqlString.contains("group by")) {
            hqlString = hqlString.substring(0, hqlString.indexOf("group by"));
        }
        if (hqlString.contains("order by")) {
            hqlString = hqlString.substring(0, hqlString.indexOf("order by"));
        }
        long total;
        if (StrUtil.isBlank(this.pageTotalStr)) {
            total = (long) entityManager.createQuery(hqlString.replace("select " + this.select, "select count(*)"))
                    .getSingleResult();
        } else {
            total = (long) entityManager.createQuery(hqlString.replace(this.select, this.pageTotalStr))
                    .getSingleResult();
        }
        List<T> retList = new ArrayList<>(list.size());
        for (Object o : list) {
            retList.add(function.apply(o));
        }
        return PageData.of(page, size, total, retList);
    }

    public static String getEntityName(Class<?> entityClass) {
        Entity entity = AnnotationUtil.getAnnotation(entityClass, Entity.class);
        if (entity == null) {
            return entityClass.getSimpleName();
        }
        return StrUtil.blankToDefault(entity.name(), entityClass.getSimpleName());
    }

    private String buildHqlWhere(Where where) {
        StringBuilder whereHql = new StringBuilder();
        if (where.getType() == ConditionType.subQuery) {
            whereHql.append(" (");
            for (int i = 0; i < where.getWhereList().size(); i++) {
                Where item = where.getWhereList().get(i);
                whereHql.append(buildHqlWhere(item));
                if (i < where.getWhereList().size() - 1) {
                    whereHql.append(" ")
                            .append(where.getLinkType().name())
                            .append(" ");
                }
            }
            whereHql.append(")");
        } else {
            Object whereValue = where.getValue();
            if (whereValue instanceof Date) {
                whereValue = DateUtil.format((Date) whereValue, DatePattern.NORM_DATETIME_FORMAT);
            }
            switch (where.getType()) {
                case equal:
                case notEqual:
                case greaterThan:
                case lessThan:
                case greaterThanOrEqual:
                case lessThanOrEqual:
                    whereHql.append(where.getColumn())
                            .append(" ")
                            .append(where.getType().getHqlValue())
                            .append(" ")
                            .append(whereValue instanceof String ? "'" : "")
                            .append(whereValue)
                            .append(whereValue instanceof String ? "'" : "")
                    ;
                    break;
                case in:
                case notIn:
                    whereHql.append(where.getColumn())
                            .append(" ")
                            .append(where.getType().getHqlValue())
                            .append(" ");
                    if (whereValue instanceof Collection) {
                        whereHql.append("(");
                        for (Object item : (Collection) whereValue) {
                            whereHql.append(item)
                                    .append(",");
                        }
                        whereHql.delete(whereHql.length() - 1, whereHql.length());
                        whereHql.append(")");
                    } else if (ArrayUtil.isArray(whereValue)) {
                        whereHql.append("(");
                        for (int i = 0; i < ArrayUtil.length(whereValue); i++) {
                            whereHql.append(String.valueOf(ArrayUtil.get(whereValue, i)));
                            if (i < ArrayUtil.length(whereValue) - 1) {
                                whereHql.append(",");
                            }
                        }
                        whereHql.append(")");
                    } else {
                        throw new CommonRuntimeException("in or not in 查询必须使用数组或集合");
                    }
                    break;
                case like:
                case notLike:
                    whereHql.append(where.getColumn())
                            .append(" ")
                            .append(where.getType().getHqlValue())
                            .append(" '%")
                            .append(whereValue)
                            .append("%'");
                    break;
                case likeLeft:
                    whereHql.append(where.getColumn())
                            .append(" ")
                            .append(where.getType().getHqlValue())
                            .append(" '%")
                            .append(whereValue)
                            .append("'");
                    break;
                case likeRight:
                    whereHql.append(where.getColumn())
                            .append(" ")
                            .append(where.getType().getHqlValue())
                            .append(" '")
                            .append(whereValue)
                            .append("%'");
                case between:
                    Object firstValue = null;
                    Object secondValue = null;
                    if (whereValue instanceof Collection && ((Collection) whereValue).size() == 2) {
                        Collection values = (Collection) whereValue;
                        firstValue = values.toArray()[0];
                        secondValue = values.toArray()[1];
                    } else if (ArrayUtil.isArray(whereValue) && ArrayUtil.length(whereValue) == 2) {
                        firstValue = ArrayUtil.get(whereValue, 0);
                        secondValue = ArrayUtil.get(whereValue, 1);
                    } else {
                        throw new CommonRuntimeException("between 的值应该为 长度为2 的集合或数组");
                    }
                    if (firstValue == null || secondValue == null) {
                        throw new CommonRuntimeException("between 的值应该为 长度为2 的集合或数组，且元素不可为空");
                    }
                    whereHql.append(where.getColumn())
                            .append(" ")
                            .append(where.getType().getHqlValue())
                            .append(" ");
                    if (firstValue instanceof String) {
                        whereHql.append("'")
                                .append(firstValue)
                                .append("' AND '")
                                .append(secondValue)
                                .append("'");
                    } else if (firstValue instanceof Date) {
                        whereHql.append("'")
                                .append(DateUtil.format((Date) firstValue, where.getDateFormat()))
                                .append("' AND '")
                                .append(DateUtil.format((Date) secondValue, where.getDateFormat()))
                                .append("'");
                    } else {
                        whereHql.append(firstValue)
                                .append(" AND ")
                                .append(secondValue);
                    }
                    break;
                case isNotNull:
                    whereHql.append(where.getColumn())
                            .append(" ")
                            .append(where.getType().getHqlValue());
                    break;
                case isNull:
                    whereHql.append(where.getColumn())
                            .append(" ")
                            .append(where.getType().getHqlValue());
                    break;
                default:
                    throw new CommonRuntimeException("请联系研发管理员升级核心框架");
            }
        }
        return whereHql.toString();
    }
}
